﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace KATS_Client.Client
{
    /// <summary>
    /// Manages the server connection
    /// </summary>
    public static class ServerConnection
    {
        /// <summary>
        /// The main connection to the server
        /// </summary>
        private static KATS_ServerReference.ServerClient server;

        /// <summary>
        /// The main connection to the server
        /// </summary>
        public static KATS_ServerReference.ServerClient Server
        {
            get
            {
                // find if the class needs a new server connection object
                bool NeedNew = false;
                if (server == null)
                {
                    NeedNew = true;
                }
                else
                {
                    bool invalidState = server.State == CommunicationState.Closed || server.State == CommunicationState.Closing || server.State == CommunicationState.Faulted;
                    if (invalidState)
                        NeedNew = true;
                }

                if (NeedNew)
                {
                    // the connection to the server has not yet been started
                    // or has entered a faulty state
                    Authenticated = false;
                    if (server != null)
                    {
                        // there is an old one, attempt to close it
                        // close the connection of the old server
                        if (server.State == System.ServiceModel.CommunicationState.Faulted)
                        {
                            try
                            {
                                Console.WriteLine("Closing server connection to make new one. " + server.State.ToString());
                                Disconnect();
                            }
                            catch (Exception)
                            {
                                // catch any exceptions thrown
                            }
                        }
                    }

                    // make a new connection to the server
                    server = MakeConnection();
                }
                // return the stored connection to the server
                return server;
            }
        }

        /// <summary>Make a new main connection in a new thread</summary>
        public static void MakeNewMainConnection_Threaded()
        {
            System.Threading.Thread makeNewConnectionThread = new System.Threading.Thread(new System.Threading.ThreadStart(MakeNewMainConnection));
            makeNewConnectionThread.Name = "Make new main connection thread";
            makeNewConnectionThread.IsBackground = true;
            makeNewConnectionThread.Start();
        }

        /// <summary>Make a new main connection</summary>
        public static void MakeNewMainConnection()
        {
            server = MakeConnection();
        }

        /// <summary>
        /// Make a new connection to the server
        /// </summary>
        /// <returns>The connection</returns>
        public static KATS_ServerReference.ServerClient MakeConnection()
        {
            // so make a new connection to the server
            KATS_ServerReference.ServerClient server = new KATS_Client.KATS_ServerReference.ServerClient();
            server.Open();
            Authenticated = Authenticate(server);
            return server;
        }

        /// <summary>
        /// Initial the connection to the server
        /// </summary>
        public static void Initialise()
        {
        }

        /// <summary>Is the connection to the server authenticated</summary>
        static bool _Authenticated = false;

        /// <summary>Is the connection to the server authenticated</summary>
        public static bool Authenticated
        {
            get { return _Authenticated; }
            private set
            {
                bool old = _Authenticated;
                _Authenticated = value;
                if (old != value)
                {
                    if (Authenticated_Changed != null)
                    {
                        Authenticated_Changed(null, null);
                    }
                }
            }
        }

        /// <summary>The authenticated status has changed</summary>
        public static event EventHandler<EventArgs> Authenticated_Changed;

        /// <summary>
        /// Static constructor
        /// </summary>
        static ServerConnection()
        {
            ServerStatus_Changed += new EventHandler<EventArgs>(ServerConnection_ServerStatus_Changed);
            Authenticated_Changed += new EventHandler<EventArgs>(ServerConnection_Authenticated_Changed);
            Program.Exiting += new EventHandler<EventArgs>(Program_Exiting);


            ConnectionKeepAlive = new System.Timers.Timer(5 * 60 * 1000);
            ConnectionKeepAlive.AutoReset = true;
            ConnectionKeepAlive.Elapsed += new System.Timers.ElapsedEventHandler(ConnectionKeepAlive_Elapsed);
            ConnectionKeepAlive.Enabled = true;


            int y = ProgressReporting.Start("Status timer starting");
            // start the auto checking of the server status
            ServerStatus_Update_Threaded();
            ServerStatus_Timer = new System.Timers.Timer(Properties.Settings.Default.ServerStatusOnlineTimerLength);
            ServerStatus_Timer.AutoReset = true;
            ServerStatus_Timer.Elapsed += new System.Timers.ElapsedEventHandler(serverStatus_Timer_Elapsed);
            ServerStatus_Timer.Enabled = true;
            ProgressReporting.End(y, "Started status timer");
        }



        /// <summary>
        /// Send a message to the server to keep it active
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void ConnectionKeepAlive_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Server.Wait(0);
        }

        /// <summary>
        /// Keep the connection alive by sending a msg to the server every 5minutes
        /// </summary>
        static System.Timers.Timer ConnectionKeepAlive = null;

        /// <summary>
        /// The server's status has changed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void ServerConnection_ServerStatus_Changed(object sender, EventArgs e)
        {
            if (!ServerStatus)
            {
                // server is now offline
                // so terminate the server connection
                Disconnect();
            }
        }

        /// <summary>The authenticated status has changed on the server.</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void ServerConnection_Authenticated_Changed(object sender, EventArgs e)
        {
            if (ServerStatus_Changed != null)
            {
                ServerStatus_Changed(null, null);
            }
        }

        /// <summary>
        /// The program is exiting
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void Program_Exiting(object sender, EventArgs e)
        {
            // exiting so disconnect
            Disconnect();
        }

        /// <summary>
        /// Attempt to disconnect the connection
        /// </summary>
        public static void Disconnect()
        {
            try
            {
                server.Close();
                server = null;
            }
            catch
            {
                // catch any errors if the server fails to DC from the server
            }
        }

        /// <summary>
        /// Authenticate with the server
        /// </summary>
        /// <param name="server">The connection to authenticate</param>
        /// <returns>Successful?</returns>
        static bool Authenticate(KATS_ServerReference.ServerClient server)
        {
            bool answer = false;
            try
            {
                answer = server.Authenticate(Encryption.Encrypt(Client.User.Username), Encryption.Encrypt(Client.User.Password));
            }
            catch (System.ServiceModel.EndpointNotFoundException)
            {
                Console.WriteLine("\nServer offline");
            }
            catch (System.ServiceModel.CommunicationException)
            {
                Console.WriteLine("\nUsername or password incorrect");
            }
            return answer;
        }

        /// <summary>The current server status</summary>
        private static bool serverStatus = false;

        /// <summary>The current server status</summary>
        public static bool ServerStatus
        {
            get
            {
                if (!ServerConnection.Authenticated)
                    return false;
                return serverStatus;
            }
            set
            {
                serverStatus = value;
                if (ServerStatus_Changed != null)
                    ServerStatus_Changed(new object(), new EventArgs());
            }
        }


        /// <summary>The server's status has changed</summary>
        public static event EventHandler<EventArgs> ServerStatus_Changed;

        /// <summary>The server status timer</summary>
        private static System.Timers.Timer ServerStatus_Timer;
        
        /// <summary>
        /// Maintains the current status of the server. Called every 5 seconds by a timer from the static constructor
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void serverStatus_Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            // timer triggered
            ServerStatus_Update();
        }

        /// <summary>Updating the server status in a new thread</summary>
        public static void ServerStatus_Update_Threaded()
        {
            System.Threading.Thread refresh = new System.Threading.Thread(new System.Threading.ThreadStart(ServerStatus_Update));
            refresh.Name = "Server status update";
            refresh.IsBackground = true;
            refresh.Start();
        }

        /// <summary>
        /// Updates the serverStatus variable
        /// </summary>
        public static void ServerStatus_Update()
        {
            // time to recheck the server
            bool currentStatus = isServerOnline();
            if (currentStatus != ServerStatus)
            {
                // status has changed
                // change the saved status
                ServerStatus = currentStatus;
            }
        }


        /// <summary>
        /// Attempts to engage server. Result dictitates wheither server is online or not.
        /// ORIGINAL SOURCE FROM:
        /// http://geekswithblogs.net/LeonidGaneline/archive/2008/05/07/wcf-availability-of-the-wcf-services.aspx
        /// "Programming WCF Services" by Juval Lowy
        /// </summary>
        /// <returns>Server online?</returns>
        private static bool isServerOnline()
        {
            // SOURCE
            // http://geekswithblogs.net/LeonidGaneline/archive/2008/05/07/wcf-availability-of-the-wcf-services.aspx
            // Function Source "Programming WCF Services" by Juval Lowy
            bool isServiceUp = true;
            try
            {
                System.ServiceModel.Configuration.ClientSection clientSection = (System.ServiceModel.Configuration.ClientSection)System.Configuration.ConfigurationManager.GetSection("system.serviceModel/client");

                string MEX_HTTP_ADDRESS = clientSection.Endpoints[0].Address.AbsoluteUri;
                int NETTCP_PORT = clientSection.Endpoints[0].Address.Port;
                int MEX_HTTP_PORT = NETTCP_PORT + 1;
                MEX_HTTP_ADDRESS = MEX_HTTP_ADDRESS.Replace("net.tcp", "http");
                MEX_HTTP_ADDRESS = MEX_HTTP_ADDRESS.Replace(NETTCP_PORT.ToString(), MEX_HTTP_PORT.ToString());

                System.ServiceModel.Description.MetadataExchangeClient mexClient = new System.ServiceModel.Description.MetadataExchangeClient(new Uri(MEX_HTTP_ADDRESS), System.ServiceModel.Description.MetadataExchangeClientMode.HttpGet);
                System.ServiceModel.Description.MetadataSet metadata = mexClient.GetMetadata();
                // if service down I get the exception
            }
            catch (System.InvalidOperationException)
            {
                //Console.WriteLine(ex.ToString());
                isServiceUp = false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                isServiceUp = false;
            }
            return isServiceUp;
        }

    }
}